/*
 * $Id: StepLogger.java,v 1.29 2010/06/17 21:30:37 navdeep_mahajan Exp $
 *
 * Copyright (C) 2006 General Electric Company. All Rights Reserved.
 *
 * This software is the confidential and proprietary information of the General
 * Electric Company (GE). You shall not disclose this software and shall use it
 * only in accordance with the terms of the license agreement you entered into
 * with GE.
 *
 * GE MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
 * NON-INFRINGEMENT. GE SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING, OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES.
 */

/**
 * 
 */
package com.ge.healthcare.autosc.common.dbLogger;

import java.io.IOException;
import java.net.UnknownHostException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import com.ge.healthcare.autosc.common.ASCLogger;
import com.ge.healthcare.autosc.common.ASCUtil;
import com.ge.healthcare.autosc.common.database.dao.IStepTypeDAO;
import com.ge.healthcare.autosc.common.database.pojo.CfgStepType;
import com.ge.healthcare.autosc.common.database.pojo.HstStepLogs;
import com.ge.healthcare.autosc.common.database.pojo.HstStepTime;
import com.ge.healthcare.autosc.common.exceptions.MBeanAccessException;
import com.ge.healthcare.autosc.common.message.converter.AutoSCMessage;
import com.ge.healthcare.autosc.common.util.ApplicationInfo;
import com.ge.healthcare.autosc.common.util.PropertyConstants;
import com.ge.healthcare.autosc.common.util.TimeFormatStandard;

/**
 * @author 212042764
 * 
 */
public class StepLogger {
	private static final String LOG_DELIM = "\n\n";
	/** Step Type Id */
	private static Long stepTypeId = null;
	private static String stepTypeName = null;
	private static String managedServerName = null;
	/** Holds step time */
	private static final ThreadLocal<HstStepTime> threadLocal = new ThreadLocal<HstStepTime>();
	public static final String TASK_TYPE = HstStepTime.TASK_TYPE;
	public static final String REQUEST_TYPE = HstStepTime.REQUEST_TYPE;
	public static final String REQUEST_LIST_TYPE = HstStepTime.REQUEST_LIST_TYPE;
	private static IStepTypeDAO stepTypeDAO = null;
	private static ILogSender logSender = null;

	/**
	 * The constructor is used by spring to update step type information in the
	 * database
	 * @throws IOException 
	 */
	public StepLogger(String stepTypeName, IStepTypeDAO stepTypeDAO)
			throws IOException {
		StepLogger.stepTypeDAO = stepTypeDAO;
		StepLogger.stepTypeName = stepTypeName;
		updateStepTypeId(stepTypeName);
	}

	/**
	 * Create Step Time VO from AutoSC Message Object
	 * 
	 * @param msgObject
	 */
	public static void logStartTime(AutoSCMessage msgObject) {
		logStartTime(msgObject.getObjectId(), msgObject.getObjectType());
	}

	/**
	 * Create step time VO and adds step type to the same
	 * 
	 * @param stepTypeName
	 *            - Step Type e.g. LBS, PSS etc
	 * @param taskId
	 *            - Task id to use
	 */
	public static void logStartTime(Long taskId, String objectType) {
		String methodName = "logStartTime";
		resetStepTime();
		ASCLogger.debug(StepLogger.class, methodName, "Entering " + methodName);
		logObjectTypeAndId(taskId, objectType);
		ASCLogger.debug(StepLogger.class, methodName,
				"Step Time Object created. Leaving");
	}

	/**
	 * This method is used if we don't know the object type and id in advance
	 * 
	 * @param taskId
	 * @param objectType
	 */
	public static void logObjectTypeAndId(Long taskId, String objectType) {
		HstStepTime stepTime = getStepTime();
		stepTime.setObjectId(taskId);
		stepTime.setObjectType(objectType);
		threadLocal.set(stepTime);
	}

	/**
	 * Create step time VO and adds step type to the same
	 */
	public static void logStartTime() {
		resetStepTime();
	}

	/**
	 * This method retrieves the step type id if defined If not defined it
	 * creates a record using the step type name
	 * 
	 * @param stepTypeName
	 *            - Step type Name
	 * @throws IOException
	 */
	private static void updateStepTypeId(String stepTypeName)
			throws IOException {
		String methodName = "updateStepTypeId";
		ASCLogger.debug(StepLogger.class, methodName, "Entering " + methodName);
		managedServerName = getServerName();
		CfgStepType stepType = stepTypeDAO.getStepType(stepTypeName,
				managedServerName);
		if (!ASCUtil.isNullOrEmptyObject(stepType)) {
			stepTypeId = stepType.getStepTypeId();
		}
		ASCLogger
				.info(StepLogger.class, methodName, "stepTypeId:" + stepTypeId);
		if (ASCUtil.isNullOrEmptyObject(stepTypeId)) {
			stepType = new CfgStepType();
			stepType.setStepType(stepTypeName);
			stepType.setServerName(managedServerName);
		}
		Properties properties = ASCUtil
				.loadPropertiesInClassPath("/build.properties");
		if (!ASCUtil.isNullOrEmptyProperties(properties)) {
			stepType.setVersion(properties.getProperty(
					PropertyConstants.BUILD_VERSION, "UNKNOWN"));
		}
		stepTypeDAO.save(stepType);
		ASCLogger.info(StepLogger.class, methodName, "Saved stepType:"
				+ stepType);
		stepTypeId = stepType.getStepTypeId();
		ASCLogger.info(StepLogger.class, methodName, "stepTypeId after save:"
				+ stepTypeId);
		ASCLogger.debug(StepLogger.class, methodName, "Leaving " + methodName);
	}

	/**
	 * Returns managed server name, for tests it returns hardware hostname
	 * 
	 * @return
	 * @throws UnknownHostException
	 */
	private static String getServerName() throws UnknownHostException {
		String serverName = null;
		String methodName = "getServerName";
		try {
			serverName = ASCUtil.getManagedServerName();
		} catch (MBeanAccessException mBeanAccessFailure) {
			ASCLogger
					.error(
							StepLogger.class,
							methodName,
							"If i am seeing this log it means i am running a test, i should never see the log in production");
			serverName = ASCUtil.getHardwareHostName();
		}
		return serverName;
	}

	/**
	 * The method logs the end time and sends the message to DBLogger Queue
	 * 
	 * @param objectId
	 *            - object Id to use
	 * @param objectType
	 *            - object type to use
	 * @param codeId
	 *            - Code ID for closing the step time
	 */
	public static void logEndTime(Long objectId, String objectType, Long codeId) {
		String methodName = "logEndTime";
		ASCLogger.debug(StepLogger.class, methodName, "Entering");
		HstStepTime stepTime = getStepTime();
		if ((ASCUtil.isNullOrEmptyObject(objectId))
				|| (ASCUtil.isNullOrEmptyObject(objectType))) {
			ASCLogger
					.error(
							StepLogger.class,
							methodName,
							new StringBuffer()
									.append(
											"Object Id and object type must be provided to log end time:\n")
									.append("objectId: ").append(objectId)
									.append("\n").append("object type: ")
									.append(objectType).toString());
			return;
		}
		if (ASCUtil.isNullOrEmptyObject(stepTime)) {
			ASCLogger.error(StepLogger.class, methodName,
					"Step Time has not been initialized : " + stepTime);
		} else {
			stepTime.setObjectId(objectId);
			stepTime.setObjectType(objectType);
			stepTime.setCodeId(codeId);
			stepTime.setEndTime(TimeFormatStandard.getNowMillis());
			sendLogs(stepTime);
		}
	}

	public static void logEndTime(Long codeId) {
		String methodName = "logEndTime";
		ASCLogger.debug(StepLogger.class, methodName, "Entering");
		HstStepTime stepTime = getStepTime();
		if (ASCUtil.isNullOrEmptyObject(stepTime)
				|| ASCUtil.isNullOrEmptyObject(stepTime.getObjectId())
				|| ASCUtil.isNullOrEmpty(stepTime.getObjectType())) {
			ASCLogger.error(StepLogger.class, methodName,
					"Step Time has not been initialized : " + stepTime);
		} else {
			stepTime.setCodeId(codeId);
			stepTime.setEndTime(TimeFormatStandard.getNowMillis());
			sendLogs(stepTime);
		}
	}

	/**
	 * @param stepTime
	 */
	private static void sendLogs(HstStepTime stepTime) {
		if ("true".equalsIgnoreCase(ApplicationInfo.getInstance().getProperty(
				PropertyConstants.STEP_LOGS_ENABLED, "true"))) {
			logSender.sendStepTimeLog(stepTime);
		}
	}

	/**
	 * Adds step log to the step time VO
	 * 
	 * @param log
	 *            - Log to be added
	 */
	public static void addStepLog(String log) {
		HstStepTime stepTime = getStepTime();
		HstStepLogs stepLog = new HstStepLogs();
		stepLog.setStepLogText(log);
		stepLog.setHstStepTime(stepTime);
		List<HstStepLogs> stepLogs = stepTime.getHstStepLogs();
		if (ASCUtil.isNullOrEmptyList(stepLogs)) {
			stepLogs.add(stepLog);
		} else {
			HstStepLogs oldLog = stepLogs.get(stepLogs.size() - 1);
			if (oldLog.getStepLogText().length() + LOG_DELIM.length()
					+ log.length() <= oldLog.getMaxLogLength()) {
				oldLog.setStepLogText(new StringBuffer().append(
						oldLog.getStepLogText()).append(LOG_DELIM).append(log)
						.toString());
			} else {
				stepLogs.add(stepLog);
			}
		}
		stepTime.setHstStepLogs(stepLogs);
		threadLocal.set(stepTime);
	}

	/**
	 * Accepts object and calls toString on it for you
	 * 
	 * @param log
	 */
	public static void addStepLog(Object log) {
		addStepLog(log.toString());
	}

	/**
	 * Allows users to reset step logs
	 */
	public static void resetStepLogs() {
		HstStepTime stepTime = (HstStepTime) threadLocal.get();
		if (!ASCUtil.isNullOrEmptyObject(stepTime)) {
			stepTime.setHstStepLogs(new ArrayList<HstStepLogs>());
			threadLocal.set(stepTime);
		}
	}

	/**
	 * Retrieves step time from thread local
	 * 
	 * @return - step time object
	 */
	private static HstStepTime getStepTime() {
		HstStepTime stepTime = (HstStepTime) threadLocal.get();
		if (ASCUtil.isNullOrEmptyObject(stepTime)) {
			stepTime = resetStepTime();
		}
		return stepTime;
	}

	private static HstStepTime resetStepTime() {
		HstStepTime stepTime = new HstStepTime();
		stepTime.setStartTime(TimeFormatStandard.getNowMillis());
		stepTime.setStepTypeId(stepTypeId);
		threadLocal.set(stepTime);
		return stepTime;
	}

	/**
	 * In case step time has not been initialized in advance then we need to
	 * tell the start time later
	 * 
	 * @param startTime
	 *            - Tells the start time
	 */
	public static void setStartTime(Timestamp startTime) {
		HstStepTime stepTime = getStepTime();
		stepTime.setStartTime(startTime);
		threadLocal.set(stepTime);
	}

	/**
	 * @return the stepTypeId
	 */
	public static Long getStepTypeId() {
		return stepTypeId;
	}

	/**
	 * @param logSender
	 *            the logSender to set
	 */
	public void setLogSender(ILogSender logSender) {
		StepLogger.logSender = logSender;
	}

	/**
	 * @return the stepTypeName
	 */
	public static String getStepTypeName() {
		return stepTypeName;
	}

	/**
	 * @return the managedServerName
	 */
	public static String getManagedServerName() {
		return managedServerName;
	}
}
